package ws

import (
	"errors"
	"gitee.com/lipore/go-nio/pkg/transport"
	"github.com/gorilla/websocket"
	"golang.org/x/net/context"
	"log"
	"net/http"
	"net/url"
)

type factory struct {
	idFactory transport.ChannelIDFactory
}

func NewWsFactory(idFactory transport.ChannelIDFactory) transport.Factory {
	return &factory{
		idFactory: idFactory,
	}
}

func (f factory) Schemes() transport.Schemes {
	return transport.Schemes{"ws"}
}

func (f factory) Listen(address *url.URL) (transport.Acceptor, error) {
	if err := f.Schemes().FixURL(address); nil != err {
		return nil, err
	}
	acceptor := NewWsAcceptor(f.idFactory)
	s := &http.Server{Addr: address.Host, Handler: acceptor.(*wsAcceptor)}
	go func() { _ = s.ListenAndServe() }()
	acceptor.(*wsAcceptor).server = s

	//TODO pass options
	return acceptor, nil
}

func NewWsAcceptor(idFactory transport.ChannelIDFactory) transport.Acceptor {
	ctxWithCancel, cancel := context.WithCancel(context.Background())
	return &wsAcceptor{
		connChan: make(chan transport.Transport),
		upgrader: websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
			CheckOrigin: func(r *http.Request) bool {
				return true
			},
		},
		idFactory: idFactory,
		ctx:       ctxWithCancel,
		cancel:    cancel,
	}
}

type wsAcceptor struct {
	connChan  chan transport.Transport
	upgrader  websocket.Upgrader
	idFactory transport.ChannelIDFactory
	ctx       context.Context
	cancel    context.CancelFunc
	server    *http.Server
}

func (w *wsAcceptor) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	writer.Header().Set("Access-Control-Allow-Origin", "*")
	conn, err := w.upgrader.Upgrade(writer, request, nil)
	if err != nil {
		log.Println(err)
		return
	}
	w.connChan <- newWsTransport(w.idFactory(), conn)
}

func (w *wsAcceptor) Accept() (transport.Transport, error) {
	conn := <-w.connChan
	if conn == nil {
		return nil, errors.New("acceptor is closed")
	}
	return conn, nil
}

func (w *wsAcceptor) Close() error {
	w.cancel()
	close(w.connChan)
	s := w.server
	w.server = nil
	return s.Close()
}
